package com.share.device.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.share.common.core.context.SecurityContextHolder;
import com.share.common.core.domain.R;
import com.share.common.core.exception.ServiceException;
import com.share.common.core.utils.bean.BeanUtils;
import com.share.common.rabbit.constant.MqConst;
import com.share.common.rabbit.service.RabbitService;
import com.share.common.security.utils.SecurityUtils;
import com.share.device.domain.*;
import com.share.device.emqx.EmqxClientWrapper;
import com.share.device.emqx.constant.EmqxConstants;
import com.share.device.service.*;
import com.share.order.api.RemoteOrderInfoService;
import com.share.order.api.domain.OrderInfo;
import com.share.rules.api.RemoteFeeRuleService;
import com.share.rules.api.domain.FeeRule;
import com.share.user.api.RemoteUserInfoService;
import com.share.user.api.domain.UserInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
@RequiredArgsConstructor
public class DeviceServiceImpl implements IDeviceService {

    private final IStationService stationService;

    private final ICabinetService cabinetService;

    private final MongoTemplate mongoTemplate;

    private final IMapService mapService;

    private final RemoteFeeRuleService remoteFeeRuleService;

    private final RemoteOrderInfoService remoteOrderInfoService;

    private final RemoteUserInfoService remoteUserInfoService;

    private final EmqxClientWrapper emqxClientWrapper;

    private final ICabinetSlotService cabinetSlotService;

    private final IPowerBankService powerBankService;

    private final RabbitService rabbitService;

    /**
     * 查询指定坐标附近的站点信息
     *
     * @param latitude  中心点纬度
     * @param longitude 中心点经度
     * @param radius    查询半径（千米）
     * @return 附近站点信息列表，包含距离、可用状态和费用规则等
     */
    @Override
    public List<StationVo> nearbyStation(String latitude, String longitude, Integer radius) {
        // 1. 构建查询条件 - 以指定坐标为中心，指定半径的圆形区域
        GeoJsonPoint centerPoint = new GeoJsonPoint(Double.parseDouble(longitude),  // GeoJsonPoint中x为经度
                Double.parseDouble(latitude)    // GeoJsonPoint中y为纬度
        );

        Distance distance = new Distance(radius, Metrics.KILOMETERS);
        Circle searchCircle = new Circle(centerPoint, distance);

        // 2. 查询MongoDB获取地理位置匹配的站点ID
        Query query = Query.query(Criteria.where("location").withinSphere(searchCircle));
        List<StationLocation> stationLocationList = this.mongoTemplate.find(query, StationLocation.class);
        if (CollectionUtils.isEmpty(stationLocationList)) {
            return null; // 无匹配站点时直接返回
        }

        // 3. 批量获取站点基础信息
        List<Long> stationIdList = stationLocationList.stream().map(StationLocation::getStationId).collect(Collectors.toList());
        List<Station> stationList = stationService.list(new LambdaQueryWrapper<Station>().in(Station::getId, stationIdList).isNotNull(Station::getCabinetId) // 过滤掉无柜机信息的站点
        );

        // 4. 批量获取柜机信息并构建映射关系
        List<Long> cabinetIdList = stationList.stream().map(Station::getCabinetId).collect(Collectors.toList());
        Map<Long, Cabinet> cabinetIdToCabinetMap = cabinetService.listByIds(cabinetIdList).stream().collect(Collectors.toMap(Cabinet::getId, cabinet -> cabinet));

        // 5. 批量获取费用规则信息并构建映射关系
        List<Long> feeRuleIdList = stationList.stream().map(Station::getFeeRuleId).collect(Collectors.toList());
        Map<Long, FeeRule> feeRuleIdToFeeRuleMap = remoteFeeRuleService.getFeeRuleList(feeRuleIdList).getData().stream().collect(Collectors.toMap(FeeRule::getId, feeRule -> feeRule));

        // 6. 组装返回数据
        List<StationVo> stationVoList = new ArrayList<>();

        stationList.forEach(station -> {
            StationVo stationVo = new StationVo();
            // 复制基础属性
            BeanUtils.copyProperties(station, stationVo);

            // 计算与中心点的距离（调用地图服务）
            Double distanceValue = mapService.calculateDistance(longitude, latitude, station.getLongitude().toString(), station.getLatitude().toString());
            stationVo.setDistance(distanceValue);

            // 获取柜机状态信息
            Cabinet cabinet = cabinetIdToCabinetMap.get(station.getCabinetId());
            // 判断是否可借用（可用充电宝数量大于0）
            stationVo.setIsUsable(cabinet.getAvailableNum() > 0 ? "1" : "0");
            // 判断是否可归还（空闲插槽数量大于0）
            stationVo.setIsReturn(cabinet.getFreeSlots() > 0 ? "1" : "0");

            // 获取并设置费用规则描述
            FeeRule feeRule = feeRuleIdToFeeRuleMap.get(station.getFeeRuleId());
            stationVo.setFeeRule(feeRule.getDescription());

            stationVoList.add(stationVo);

        });

        return stationVoList;
    }

    /**
     * 获取指定站点的详细信息，包括距离、可用状态和费用规则
     *
     * @param id        站点ID
     * @param latitude  当前位置纬度
     * @param longitude 当前位置经度
     * @return 站点详情VO对象
     */
    @Override
    public StationVo getStation(Long id, String latitude, String longitude) {

        // 1. 查询站点基础信息
        Station station = stationService.getById(id);
        StationVo stationVo = new StationVo();
        BeanUtils.copyProperties(station, stationVo); // 复制基础属性到VO

        // 2. 计算当前位置到站点的距离（调用地图服务）
        Double distance = mapService.calculateDistance(
                longitude,                         // 当前位置经度
                latitude,                          // 当前位置纬度
                station.getLongitude().toString(), // 站点经度
                station.getLatitude().toString()   // 站点纬度
        );
        stationVo.setDistance(distance);

        // 3. 获取柜机状态信息
        Cabinet cabinet = cabinetService.getById(station.getCabinetId());

        // 判断是否可借用（可用充电宝数量大于0）
        stationVo.setIsUsable(cabinet.getAvailableNum() > 0 ? "1" : "0");

        // 判断是否可归还（空闲插槽数量大于0）
        stationVo.setIsReturn(cabinet.getFreeSlots() > 0 ? "1" : "0");

        // 4. 获取并设置费用规则信息
        FeeRule feeRule = remoteFeeRuleService.getFeeRule(
                station.getFeeRuleId()
        ).getData();
        stationVo.setFeeRule(feeRule.getDescription());

        return stationVo;
    }

    /**
     * 扫码充电
     */
    @Override
    public ScanChargeVo scanCharge(String cabinetNo) {
        log.info("用户扫码请求充电，柜机编号：{}", cabinetNo); // 记录用户操作起点
        // 扫码充电返回对象
        ScanChargeVo scanChargeVo = new ScanChargeVo();

        // 免押金判断
        log.debug("开始获取用户信息，用户ID：{}", SecurityContextHolder.getUserId());
        R<UserInfo> userInfoResult = remoteUserInfoService.getUserInfo(SecurityContextHolder.getUserId());
        if (R.FAIL == userInfoResult.getCode()) {
            log.error("获取用户信息失败，错误码：{}，错误信息：{}", userInfoResult.getCode(), userInfoResult.getMsg());
            throw new ServiceException(userInfoResult.getMsg());
        }
        UserInfo userInfo = userInfoResult.getData();
        if (null == userInfo) {
            log.error("获取用户信息为空，用户ID：{}", SecurityContextHolder.getUserId());
            throw new ServiceException("获取用户信息失败");
        }
        if ("0".equals(userInfo.getDepositStatus())) {
            log.warn("用户未申请免押金，用户ID：{}", SecurityContextHolder.getUserId());
            throw new ServiceException("未申请免押金使用");
        }
        log.info("用户免押金状态校验通过，用户ID：{}", SecurityContextHolder.getUserId());

        log.debug("开始查询用户未完成订单，用户ID：{}", SecurityContextHolder.getUserId());
        R<OrderInfo> orderInfoResult = remoteOrderInfoService.getNoFinishOrder(SecurityUtils.getUserId());
        if (R.FAIL == orderInfoResult.getCode()) {
            log.error("查询未完成订单失败，错误码：{}，错误信息：{}", orderInfoResult.getCode(), orderInfoResult.getMsg());
            throw new ServiceException(orderInfoResult.getMsg());
        }
        OrderInfo orderInfo = orderInfoResult.getData();
        if (null != orderInfo) {
            if ("0".equals(orderInfo.getStatus())) {
                log.info("用户存在未归还充电宝，订单ID：{}", orderInfo.getId());
                scanChargeVo.setStatus("2");
                scanChargeVo.setMessage("有未归还充电宝，请归还后使用");
                return scanChargeVo;
            }
            if ("1".equals(orderInfo.getStatus())) {
                log.info("用户存在未支付订单，订单ID：{}", orderInfo.getId());
                scanChargeVo.setStatus("3");
                scanChargeVo.setMessage("有未支付订单，去支付");
                return scanChargeVo;
            }
        }
        log.info("用户订单状态校验通过，用户ID：{}", SecurityContextHolder.getUserId());

        // 获取可用充电宝信息
        log.debug("开始检查柜机可用充电宝，柜机编号：{}", cabinetNo);
        AvailableProwerBankVo availableProwerBankVo = this.checkAvailableProwerBank(cabinetNo);
        if (null == availableProwerBankVo) {
            log.warn("未找到可用充电宝，柜机编号：{}", cabinetNo);
            throw new ServiceException("无可用充电宝");
        }
        if (!StringUtils.isEmpty(availableProwerBankVo.getErrMessage())) {
            log.warn("获取可用充电宝失败，错误信息：{}，柜机编号：{}", availableProwerBankVo.getErrMessage(), cabinetNo);
            throw new ServiceException(availableProwerBankVo.getErrMessage());
        }
        log.info("已获取最优充电宝，电量：{}%，充电宝编号：{}，柜机编号：{}",
                availableProwerBankVo.getElectricity(),
                availableProwerBankVo.getPowerBankNo(),
                cabinetNo);

        // 生成借取指令，弹出充电宝
        JSONObject object = new JSONObject();
        object.put("uId", SecurityContextHolder.getUserId());
        object.put("mNo", "mm" + RandomUtil.randomString(8));
        object.put("cNo", cabinetNo);
        object.put("pNo", availableProwerBankVo.getPowerBankNo());
        object.put("sNo", availableProwerBankVo.getSlotNo());
        String topic = String.format(EmqxConstants.TOPIC_SCAN_SUBMIT, cabinetNo);

        log.info("准备下发充电宝弹出指令，主题：{}，消息内容：{}", topic, object.toJSONString());
        emqxClientWrapper.publish(topic, object.toJSONString());
        log.info("充电宝弹出指令已发送，主题：{}，消息ID：{}", topic, object.getString("mNo"));

        scanChargeVo.setStatus("1");
        log.info("扫码充电流程完成，用户ID：{}，柜机编号：{}，充电宝编号：{}",
                SecurityContextHolder.getUserId(),
                cabinetNo,
                availableProwerBankVo.getPowerBankNo());
        return scanChargeVo;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void unlockSlot(CabinetSlot cs) {
        CabinetSlot cabinetSlot = cabinetSlotService.getById(cs.getId());

        if ("2".equals(cabinetSlot.getStatus())) {
            // 状态（1：占用 0：空闲 2：锁定）
            cabinetSlot.setStatus("1");
            cabinetSlot.setUpdateTime(new Date());
            cabinetSlotService.updateById(cabinetSlot);
        }
    }

    /**
     * 根据柜机编号获取一个可用最优的充电宝
     *
     * @param cabinetNo 柜机编号
     * @return 可用充电宝信息
     */
    public AvailableProwerBankVo checkAvailableProwerBank(String cabinetNo) {
        log.debug("开始查找可用充电宝，柜机编号：{}", cabinetNo);
        AvailableProwerBankVo availableProwerBankVo = new AvailableProwerBankVo();

        Cabinet cabinet = cabinetService.getOne(new LambdaQueryWrapper<Cabinet>().eq(Cabinet::getCabinetNo, cabinetNo));
        if (cabinet.getAvailableNum() == 0) {
            log.warn("柜机无可用充电宝，柜机编号：{}", cabinetNo);
            availableProwerBankVo.setErrMessage("无可用充电宝");
            return availableProwerBankVo;
        }

        // 获取插槽列表
        log.debug("开始查询柜机插槽信息，柜机ID：{}", cabinet.getId());
        List<CabinetSlot> cabinetSlotList = cabinetSlotService.list(new LambdaQueryWrapper<CabinetSlot>().eq(CabinetSlot::getCabinetId, cabinet.getId()).eq(CabinetSlot::getStatus, "1") // 状态（1：占用 0：空闲 2：锁定）
        );
        log.debug("获取到{}个已占用插槽，柜机ID：{}", cabinetSlotList.size(), cabinet.getId());

        // 获取插槽对应的充电宝id列表
        List<Long> powerBankIdList = cabinetSlotList.stream().filter(item -> null != item.getPowerBankId()).map(CabinetSlot::getPowerBankId).collect(Collectors.toList());

        // 获取可用充电宝列表
        log.debug("开始查询可用充电宝，充电宝ID列表：{}", powerBankIdList);
        List<PowerBank> powerBankList = powerBankService.list(new LambdaQueryWrapper<PowerBank>().in(PowerBank::getId, powerBankIdList).eq(PowerBank::getStatus, "1"));
        if (CollectionUtils.isEmpty(powerBankList)) {
            log.warn("未找到可用充电宝，柜机编号：{}", cabinetNo);
            availableProwerBankVo.setErrMessage("无可用充电宝");
            return availableProwerBankVo;
        }
        log.debug("获取到{}个可用充电宝，柜机编号：{}", powerBankList.size(), cabinetNo);

        // 根据电量降序排列
        if (powerBankList.size() > 1) {
            Collections.sort(powerBankList, (o1, o2) -> o2.getElectricity().compareTo(o1.getElectricity()));
            log.debug("充电宝已按电量降序排列，柜机编号：{}", cabinetNo);
        }

        // 获取电量最多的充电宝
        PowerBank powerBank = powerBankList.get(0);
        log.debug("选择最优充电宝，电量：{}%，充电宝ID：{}", powerBank.getElectricity(), powerBank.getId());

        // 获取电量最多的充电宝插槽信息
        CabinetSlot cabinetSlot = cabinetSlotList.stream().filter(item -> null != item.getPowerBankId() && item.getPowerBankId().equals(powerBank.getId())).findFirst().orElse(null); // 修复潜在的NPE问题

        if (cabinetSlot == null) {
            log.error("未找到对应插槽信息，充电宝ID：{}，柜机编号：{}", powerBank.getId(), cabinetNo);
            availableProwerBankVo.setErrMessage("系统异常，请稍后再试");
            return availableProwerBankVo;
        }

        // 锁定柜机卡槽
        log.info("准备锁定卡槽，卡槽编号：{}，柜机编号：{}", cabinetSlot.getSlotNo(), cabinetNo);
        cabinetSlot.setStatus("2");
        cabinetSlotService.updateById(cabinetSlot);
        log.info("卡槽锁定成功，卡槽编号：{}，柜机编号：{}", cabinetSlot.getSlotNo(), cabinetNo);

        // 设置返回对象
        availableProwerBankVo.setPowerBankNo(powerBank.getPowerBankNo());
        availableProwerBankVo.setSlotNo(cabinetSlot.getSlotNo());
        availableProwerBankVo.setElectricity(powerBank.getElectricity());

        log.info("已找到最优可用充电宝，充电宝编号：{}，卡槽编号：{}，电量：{}%，柜机编号：{}", powerBank.getPowerBankNo(), cabinetSlot.getSlotNo(), powerBank.getElectricity(), cabinetNo);

        // 扫码后未弹出充电宝等情况，延迟解锁
        rabbitService.sendDealyMessage(MqConst.EXCHANGE_DEVICE, MqConst.ROUTING_UNLOCK_SLOT, JSONObject.toJSONString(cabinetSlot), MqConst.CANCEL_UNLOCK_SLOT_DELAY_TIME);

        return availableProwerBankVo;
    }
}
